package com.oedtech.esl.service.nettyService;

import com.oedtech.esl.controller.WebSocketTest;
import com.oedtech.esl.model.esl.EslFirmware;
import com.oedtech.esl.model.esl.EslStatus;
import com.oedtech.esl.model.esl.EslTab;
import com.oedtech.esl.redis.RedisUtils;
import com.oedtech.esl.repository.esl.ApDao;
import com.oedtech.esl.repository.esl.EslFirmwareDao;
import com.oedtech.esl.repository.esl.EslStatusDao;
import com.oedtech.esl.repository.esl.EslTabDao;
import com.oedtech.esl.repository.sys.StoreDao;
import com.oedtech.esl.service.SNService.InterfacesService;
import com.oedtech.esl.socket.BsChannelMap;
import com.oedtech.esl.utils.HttpUtils;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import org.apache.xmlbeans.impl.xb.xmlconfig.Extensionconfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.transaction.Transactional;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;

@Service
public class DataService extends BsBaseService{


    private final static Logger LOGGER= LoggerFactory.getLogger(DataService.class);
    @Autowired
    private EslTabDao eslTabDao;
    @Autowired
    private StoreDao storeDao;

    @Autowired
    private EslStatusDao eslStatusDao;
    @Autowired
    private EslFirmwareDao eslFirmwareDao;

    @Autowired
    private InterfacesService interfacesService;

    @Autowired
    private ApDao apDao;

    public static DataService dataService;
    @PostConstruct
    public void init(){
        dataService=this;
    }

    @Value("${channelList}")
    private String apChannelList;  //基站信道列表
    public final static Map<String ,Channel> bsChannelMap= BsChannelMap.getInstance();



    private static final SimpleDateFormat SDF=new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");//日期格式便于拆分

    /**
     * 与晶曦基站握手
     * @param flag      工厂测试模式标识，目前统一为0，0=正常模式，0xfa=工厂测试模式
     */
    public void ws(Channel channel, int flag){
        int eslNum=1;

        int wakeUp=0;           //唤醒数量
        byte[] sz={
                0x1b,                                                //0
                22,                                                  //1
                'P',                                                 //2
                0,0,                                                 //3,4
                0,0,0,0,                                       //5,6,7,8,
//---------------------------------------------------
                '3',                          //(1) sub cmd          //9
                0,0,0,0,0,0,0, //(7) datetime[7]      //10,11,12,13,14,15,16,
                0,0,                          //(2) esl_num[2]       //17,18
                0,0,                          //(2) 授权代理:休眠周期//19,20
                0,0,                          //(2) reserved         //21.22
                //(total 14)
//---------------------------------------------------
                0                                                //23

        };
        sz[3]=(byte)appID;
        sz[4]=(byte)(appID>>8);
        for(int i=0;i<4;i++){
            sz[i+5]=0;
        }

        String dateStr=SDF.format(new Date());
        String []dateStrs=dateStr.split("-");
        int x=Integer.parseInt(dateStrs[0])/100;   //获取年份前两位如2019的20
        sz[10]=(byte)((x/10)*16+(x%10));           //时间转成字节的算法，由晶曦提供
        x=Integer.parseInt(dateStrs[0])%100;      //获取年份后两位如2019的19
        sz[11]=(byte)((x/10)*16+(x%10));
        for(int i=1;i<6;i++){
            x=Integer.parseInt(dateStrs[i]);
            sz[i+11]=(byte)((x/10)*16+(x%10));
        }
        sz[17]=(byte)eslNum;
        sz[18]=(byte)(eslNum>>8);           //高8位统一放置在后面
        sz[19]=(byte)wakeUp;
        sz[20]=(byte)(wakeUp>>8);
        sz[21]=(byte)(flag==1?0xfa:0);

        sz[23]=getSum(sz);
        channelWrite(channel,sz);
    }






    /**
     *    分类处理接收的数据的方法
     * @param channel     基站socket
     * @param szRecvBuf  接收到的数据
     */
    @Transactional
    public void dataManager(Channel channel,int[] szRecvBuf){

        if(!channel.isWritable())return;
        String apid = findAPID(channel);

        String tinyIP=(szRecvBuf[5])+"."+(szRecvBuf[6])+"."+(szRecvBuf[7])+"."+(szRecvBuf[8]);
        if(szRecvBuf[2]=='p'&&szRecvBuf[9]=='3') {  //p3
//            LOGGER.info("cmd:p3--apid:[{}]",apid);

            ws(channel,0);
            return;
        }

        if (szRecvBuf[2] == 'p' && szRecvBuf[9] == '8'){
            LOGGER.info("cmd:p8--apid:[{}]",apid);
            releaseChannel(szRecvBuf,apid);
            return;
        }
        if (szRecvBuf[2] == 'p' && szRecvBuf[9] == '9'){
            LOGGER.info("cmd:p9--apid:[{}]",apid);
            return;
        }
        if (szRecvBuf[2] == 'p' && szRecvBuf[9] == '6'){
            LOGGER.info("cmd:p6--apid:[{}]",apid);
            p6Manager(channel,szRecvBuf);
            return;
        }
        if (szRecvBuf[2] == 'p' && szRecvBuf[9] == '7'){
            LOGGER.info("cmd:p7--apid:[{}]",apid);
            p7Manager(channel,szRecvBuf);
            return;
        }
        NettyServer.ex.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    dataManagerForIO(channel,szRecvBuf,apid,tinyIP);
                } catch (Exception e) {
                    LOGGER.error("dataManager-error",e);
                    channel.close();
                }
            }
        });
    }

    @Transactional
    public void dataManagerForIO(Channel channel,int[] szRecvBuf,String apid,String tinyIP) throws IOException {

        if(szRecvBuf[2] == 'e' && szRecvBuf[9] == 'y'){  //推送ka数据包，基站应答 ey
            LOGGER.info("cmd:ey,apid:[{},]tinyIP:[{}]",apid,tinyIP);
            eslTabDao.setSuccessUpdate(tinyIP);
            interfacesService.sendUpdateResult(tinyIP,"0",null);          //更新成功发送
            WebSocketTest.sendAll("ey", tinyIP, null);

            return;
        }

        if (szRecvBuf[2] == 'r' && szRecvBuf[9] == 'y') {        //ry 基站返回更新成功
            if(szRecvBuf[10]=='n'){
                WebSocketTest.sendAll("ry", tinyIP, null);  //通过webSocket将更新成功的标签信息返回给页面
                LOGGER.info("cmd:ry->n,bsIP:[{},]tinyIP:[{}]",apid,tinyIP);
                eslTabDao.setFailUpdate(tinyIP);
                interfacesService.sendUpdateResult(tinyIP,"1000","价签刷新失败");      //更新失败发送
                return;
            }
            eslTabDao.setSuccessUpdate(tinyIP);
            WebSocketTest.sendAll("ry", tinyIP, null);  //通过webSocket将更新成功的标签信息返回给页面
            interfacesService.sendUpdateResult(tinyIP,"0",null);          //更新成功发送
            LOGGER.info("cmd:ry->y,bsIP:[{},]tinyIP:[{}]",apid,tinyIP);
            return;
        }

        if (szRecvBuf[2] == 'a' && szRecvBuf[9] == 'a') {         //a9  标签login
            LOGGER.info("cmd:aa--apid:[{}],tinyIP:[{}],gid[{}],key:[{}]",apid,tinyIP,szRecvBuf[10]+(szRecvBuf[11]<<8),szRecvBuf[14]);
//            WebSocketTest.sendAll("a9",null,null);

            aaManager(channel, szRecvBuf,tinyIP,apid);

            return;
        }
        if(szRecvBuf[2] == 'p' && szRecvBuf[9] == 16){
            LOGGER.info("get p16,apid[{}]",apid);
            p16Manager(apid,channel,szRecvBuf);

        }

        /*
            基站回应 p14
         */
        if(szRecvBuf[2] == 'p' && szRecvBuf[9] == 0x0e){
            LOGGER.info("p14,apid:[{}]",apid);
            int tagNum=szRecvBuf[10]+(szRecvBuf[11]<<8);
            Set<EslStatus> list = new HashSet<>();
            if(tagNum==0){
                sendP14(channel);
                return;
            }
            for(int i=0;i<tagNum;i++){
                String theTinyIP=szRecvBuf[i*6+12]+"."+szRecvBuf[i*6+13]+"."+szRecvBuf[i*6+14]+"."+szRecvBuf[i*6+15];
                String battery=szRecvBuf[i*6+16]+"";
                String rssiVal=getRssiVal(szRecvBuf[i*6+17]);

                list.add(new EslStatus(theTinyIP,new Date(),battery,rssiVal));
            }
            eslStatusDao.saveAll(list);
        }

    }







    //

    /**
     * 推送休眠K0
     * @param channel   基站socket
     * @param tinyIP   标签ip
     */
    public void pushK0(Channel channel, String tinyIP){
        byte[]sz3={
                0x1C,                                                //0
                34,                                                  //1      总字节长度-2的长度（除掉头字节及本身）
                'K',                                                 //2
                0,0,                                                 //appId,即商店ID     3,4
                0,0,0,0,                                       //标签tinyIP       5,6,7,8,
//---------------------------------------------------
                '0',                                            // sub cmd  9
                0,0,                             //imgSize,可以理解为图形描述字文件大小
                0,                                    //1   Ka数据包数量，晶曦基站均为1；
                20,18,5,15,2,15,24,33,          //dateTime
                0,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,
                0x08,           //休眠时间（秒）
                0x00,0x00,
                0x00                //data[35]

        };
        sz3[3]=(byte)appID;
        sz3[4]=(byte)(appID>>8);
        String[]tinyIp=tinyIP.split("\\.");
        for(int i=0;i<4;i++){
            sz3[5+i]=(byte)(Integer.parseInt(tinyIp[i]));

        }
        sz3[35]=getSum(sz3);
        LOGGER.info("response K0--tinyIP:[{}]",tinyIP);
        channelWrite(channel,sz3);
    }


    /**
     * 推送K0，Ka
     * @param channel   基站socket
     * @param eslTab   标签类
     */
    protected void pushK0Ka(Channel channel, EslTab eslTab) throws IOException{
        String imgPath="sn_img_bin/"+eslTab.getStoreCode()+"/"+eslTab.getTinyIP()+"-"+eslTab.getDefaultPageId()+".bin";
        File file=new File(imgPath);  //图形描述字文件
        if(!file.exists()){
            LOGGER.warn("imgFile not found，response k0--tinyIP:[{}],imgPath[{}]",eslTab.getTinyIP(),imgPath);
//            eslTabDao.resetDownTimes(eslTab.getTinyIP());
            pushK0(channel,eslTab.getTinyIP());
            return;
        }

        byte[] sz2=new byte[(int)file.length()];                    //sz2字节数组用于保存从图形描述字文件读到的内容


        FileInputStream fis=new FileInputStream(file);
        fis.read(sz2);
        fis.close();

        byte[] sz={                                              //Ka数据包头
                0x1c,                                                //0
                0,                                                  //1
                'K',                                                 //2
                0,0,                                                 //3,4
                0,0,0,0,                                      //5,6,7,8,
//---------------------------------------------------
                'a',                          //(1) sub cmd          //9
                0x00,0x00                    //  10,11
        };
        sz[3]=(byte)appID;
        sz[4]=(byte)(appID>>8);
        int a=sz.length;
        sz= Arrays.copyOf(sz,sz.length+sz2.length+1);            //sz与sz2拼接，最终长度为sz长度+sz2长度+1，最后一位存储校验和
        System.arraycopy(sz2,0,sz,a,sz2.length);                     //sz与sz2拼接



//------------------------------------------------------------------------------
        byte b1=(byte)sz2.length;
        byte b2=(byte)(sz2.length>>8);
        byte[]sz3={                                                        //k0数据包
                0x1C,                                                //0
                34,                                                  //1
                'K',                                                 //2
                0,0,                                                 //3,4
                0,0,0,0,                                      //5,6,7,8,
//---------------------------------------------------
                '0',                          //(1) sub cmd          //9
                b1,b2,                             //b1,b2
                1,                                    //1
                20,18,5,15,2,15,24,33,
                0,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,
                0x08,   //休眠时间（秒）
                0x00,0x00,
                0x00                //data[35]

        };
        sz3[3]=(byte)appID;
        sz3[4]=(byte)(appID>>8);
        String[]tinyIp=eslTab.getTinyIP().split("\\.");
        for(int i=0;i<4;i++){
            sz3[5+i]=(byte)(Integer.parseInt(tinyIp[i]));
            sz[5+i]=(byte)(Integer.parseInt(tinyIp[i]));

        }
        sz3[35]=getSum(sz3);
        //------------------sz校验和start
        sz[sz.length-1]=getSum(sz);
        //-------------------sz校验和end
//----------------------------------------------------------------------------------

        channelWrite(channel,sz3);
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            LOGGER.error("sleep error",e);
        }
        channelWrite(channel,sz);
        LOGGER.info("pushK0Ka--tinyIP:[{}]",eslTab.getTinyIP());


    }








    /**
     *   处理p6（请求信道）请求
     * @param szRecvBuf  接收到的基站发送的字节数组
     * @throws IOException   抛出IO异常，该异常由调用的socketWrite()方法抛出
     */
    public void p6Manager(Channel   channel,int[]szRecvBuf) {
        byte []sz={
                0x1B,                                                //0
                9+48,                                                //1
                'P',                                                 //2
                0,0,                                                 //3,4
                0,0,0,0,                                       //5,6,7,8,
//---------------------------------------------------
                '6',                          //(1) sub cmd          //9
                0,0,0,0,0,0,0,0,0,0,          //AvailableChannelList[46]  //10
                0,0,0,0,0,0,0,0,0,0,
                0,0,0,0,0,0,0,0,0,0,
                0,0,0,0,0,0,0,0,0,0,
                0,0,0,0,0,0,
                0,0,                          //(2) reserved         //56.57
                //(total 48)
//---------------------------------------------------
                0                                                    //58
        };
        sz[3]=(byte)appID;
        sz[4]=(byte)(appID>>8);
        String apid=findAPID(channel);
        for(int i=0;i<4;i++){
            sz[i+5]=0;
        }

        String[] cnList = releaseChannel(szRecvBuf,apid);  //释放信道，并返回释放后可用的信道
        if(cnList==null)return;    //信道返回null,表示操作失败，此次p6请求不做应答
        for(int i=0;i<cnList.length;i++){
            sz[i+10]=(byte)Integer.parseInt(cnList[i]);
        }
        sz[56]=0;
        sz[sz.length-1]=getSum(sz);
        channelWrite(channel,sz);
    }

    /**
     *   释放信道
     * @param szRecvBuf    接收到基站发送的信息字节数组
     * @return              返回处理后的信道号列表
     */
    @Transactional
    public String[] releaseChannel(int[] szRecvBuf,String apid)  {
        int[]channel=new int[channelNum];
        for(int i=0;i<channelNum;i++){    //取出基站发送的信道
            channel[i]=szRecvBuf[i+10];
        }
        LOGGER.info("release channel,ap:[{}],channel:[{}],[{}]",apid,channel[0],channel[1]);
        boolean flag=false;   //记录是否有释放信道
//           String storeId= env.getProperty(apid);

//            StoreChannelList scl=storeChannelListDao.findOne(storeId);
        String channelList=storeDao.getChangeList(apid);
        if(channelList==null){
            return null;
//            channelList=apChannelList;
        }
        String[] cnList = channelList.trim().split("\\|");
        for (int i = 0; i < cnList.length; i++) {
            for (int j = 0; j < channel.length; j++) {
                if (channel[j] == Integer.parseInt(cnList[i]) && channel[j] < 128) {
                    cnList[i] = (channel[j] + 128) + "";
                    flag = true;
                }
            }
        }
        if (flag) {
            StringBuilder sb = new StringBuilder("");
            for (String cn : cnList) {
                sb.append(cn).append("|");
            }

            storeDao.changeChannelList(sb.toString(), apid);

        }

        return cnList;
    }

    /**
     * 应答基站的p7（确认信道）请求
     * @param szRecvBuf  接收到的信息字节数组
     * @throws IOException   抛出IO异常，该异常由调用的socketWrite（）方法抛出
     */
    public void p7Manager(Channel channel,int[]szRecvBuf)  {
        byte[] sz={
                0x1B,                                                //0
                9+5,                                                 //1
                'P',                                                 //2
                0,0,                                                 //3,4
                0,0,0,0,                                       //5,6,7,8,
//---------------------------------------------------
                '7',                          //(1) sub cmd          //9
                0,                                                   //10
                0,0,                                                 //11,12 将确认OK的信道号在发回给ap，ap再次确认下
                0,0,                                                 //13,14 将确认OK的信道号在发回给ap，ap再次确认下
                //(total 3)
//---------------------------------------------------
                0                                                    //15
        };
        sz[3]=(byte)appID;
        sz[4]=(byte)(appID>>8);
        String apid = findAPID(channel);
        for(int i=0;i<4;i++){
            sz[i+5]=0;
        }
        int[]channels=new int[channelNum];
        for(int i=0;i<channelNum;i++){               //从接收到的字节数组中提取信道号，channelNum=每个基站的信道数
            channels[i]=szRecvBuf[i+10];
        }

//            String storeId= env.getProperty(apid);
//            StoreChannelList scl=storeChannelListDao.findOne(storeId);
//            if(scl==null){
//                return;
//            }
        String channelList=storeDao.getChangeList(apid);

        if(channelList==null){
            return;
//            channelList=apChannelList;

        }
        String[] cnList = channelList.trim().split("\\|");
        int num=0;
        for(int i=0;i<cnList.length;i++){
            for(int j=0;j<channels.length;j++){
                if(channels[j]+128==Integer.parseInt(cnList[i])){
                    cnList[i]=channels[j]+"";
                    num++;
                }
            }
        }
//            if(num==channel.length)sz[10]='1';
        if(num>0)sz[10]='1';
        else sz[10]='0';
//            sz[11]=(byte)channel[0];
        for(int i=0;i<channels.length;i++){
            sz[i+11]=(byte)channels[i];
        }
        LOGGER.info("p7channel,apid:[{}],channel:[{}],[{}]",apid,channels[0],channels[1]);
        sz[sz.length-1]=getSum(sz);
        if(sz[10]=='1') {
            StringBuilder sb = new StringBuilder("");
            for (String cn : cnList) {
                sb.append(cn).append("|");
            }
            storeDao.changeChannelList(sb.toString(), apid);
            apDao.updateChannel(channels[0] + "," + channels[1], apid);
        }
//            channelInUsed="";
        channelWrite(channel,sz);

    }


    /**
     * 主动向基站发送p8（删除基站数据库）指令
     *@param channel   基站socket
     * @throws IOException  抛出IO异常，该异常由socketWrite（）方法抛出
     */
    public void p8Send(Channel channel) {
        if(channel==null){
            LOGGER.warn("channel is null");
            return;
        }
        byte[]sz={
                0x1B,                                                //0
                13,                                                 //1
                'P',                                                 //2
                0,0,                                                 //3,4   appID
                0,0,0,0,                                       //5,6,7,8, //基站ip
//---------------------------------------------------
                '8',                          //(1) sub cmd          //9
                0,0,0,0,                                             //10,11,12,13 保留字节
                //(total 3)
//---------------------------------------------------
                0                                                    //14
        };
        sz[3]=(byte)appID;
        sz[4]=(byte)(appID>>8);
        for(int i=0;i<4;i++){
            sz[i+5]=0;
        }

        sz[sz.length-1]=getSum(sz);
        channelWrite(channel,sz);
    }

    /**
     * 向基站发送p9请求，p9请求的作用是让某个晶曦基站下的所有标签重新登录
     * @param channel   基站socket
     * @throws IOException  抛出IO异常，该异常主要在调用socket的输出流的write方法时发生
     */
    public void p9Send(Channel channel) {
        if(channel==null){
            LOGGER.warn("channel is null");
            return;
        }
        byte[]sz ={
                0x1B,                                                //0
                14,                                                 //1
                'P',                                                 //2
                0,0,                                                 //3,4
                0,0,0,0,                                      //5,6,7,8,
//---------------------------------------------------
                '9',                          //(1) sub cmd          //9
                0,                                                   //10
                0,0,
                0,0,
                //(total 3)
//---------------------------------------------------
                0                                                    //15
        };
        sz[3]=(byte)appID;
        sz[4]=(byte)(appID>>8);
        for(int i=0;i<4;i++){
            sz[i+5]=0;
        }
        sz[sz.length-1]=getSum(sz);
        channelWrite(channel,sz);

    }



    public static String getRssiVal(int rssi){
        int result;
        if (rssi == 0) {            // 0
            result = -90;
        } else if (rssi == 1) {
            result = -85;
        } else if (rssi == 2) {
            result = -80;
        } else if (rssi == 3) {
            result = -75;
        } else if (rssi < 8) {
            result = -70;
        } else if (rssi < 14) {     // [5, 14)
            result = -65;
        } else if (rssi < 21) {     // [14, 21)
            result = -60;
        } else if (rssi < 26) {     // [21, 26)
            result = -55;
        } else if (rssi < 30) {     // [26, 30)
            result = -50;
        } else if (rssi < 34) {     // [30, 34)
            result = -45;
        } else if (rssi < 35) {     // [34, 35)
            result = -40;
        } else if (rssi < 38) {     // [35, 38)
            result = -35;
        } else {
            result = -25;           // [38, +oo)
        }
        return result+"";

    }

    /**
     * 将数组打印到控制台
     * @param szRecvBuf
     */
    public void printBsMsg(int[] szRecvBuf) {
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<szRecvBuf.length;i++){
            sb.append("("+i+")"+szRecvBuf[i]+",");
        }

        System.out.println(sb.toString());
    }

    /**
     * write方法
     * 发送数据给基站
     * @param datas   待发送的字节数据
     */
    public void channelWrite(Channel channel, byte[] datas) {
        ;
//        channel.eventLoop().execute(new Runnable() {
//            @Override
//            public void run() {
//                channel.writeAndFlush(Unpooled.directBuffer().writeBytes(datas));
//            }
//        });
        ;
        channel.writeAndFlush(Unpooled.copiedBuffer(datas));
//        channel.writeAndFlush(Unpooled..directBuffer().writeBytes(datas));

    }


    /**
     * 根据socket找到对应的基站ID
     * @param channel  基站socket
     * @return  基站ID
     */
    protected String findAPID(Channel channel){
        Set<Map.Entry<String,Channel>> entrySets= bsChannelMap.entrySet();
        for(Map.Entry<String,Channel> entry:entrySets){
            if(entry.getValue()==channel){
                return entry.getKey();
            }
        }
        return null;
    }


    /**
     * aa协议管理
     * @param channel
     * @param szRecvBuf
     * @param tinyIP
     * @param networkIP
     * @throws IOException
     */
    @Transactional
    public void aaManager(Channel channel, int[]szRecvBuf, String tinyIP, String networkIP) throws IOException {


        int GID_index=szRecvBuf[10]+(szRecvBuf[11]<<8);
        String batlevel=szRecvBuf[12]+"";               //电量
        String rssiVal=getRssiVal(szRecvBuf[13]);       //信号强度
        int keyStatus=szRecvBuf[14];
        String versionStr=(szRecvBuf[15]+szRecvBuf[16]*256)+"-"+szRecvBuf[17];   //2018-11 新算法
        EslTab eslTab = eslTabDao.findById(tinyIP).orElse(null);

        eslStatusDao.insertOrUpdate(tinyIP,batlevel,rssiVal);   //价签登录则更新一次心跳
        /**
         * 查询同一个基站下是否有重复的gid;
         */
        String oldTinyIP=eslTabDao.findtinyIPByGid(GID_index,networkIP);
        if(oldTinyIP!=null&&!tinyIP.equals(oldTinyIP)){
            eslTabDao.deleteById(oldTinyIP);
        }
        if(szRecvBuf[14]==0&&eslTab!=null){
            pushK0(channel, tinyIP);
            return;
        }

        /**
         * 记录价签登录时间点
         */
        eslTabDao.updateLoginTime(tinyIP);
        /**
         * 假如价签的基站变更，则对之前的基站发送p17删除该价签的gid
         */
        if(eslTab!=null&&!networkIP.equals(eslTab.getNetworkIP())){
            try{
                sendP17(eslTab);
            }catch (Exception e){
                LOGGER.warn("sendP17 error:[{}]",e.getMessage());
            }

        }
        if(eslTab!=null&&keyStatus==8){

            if(eslTab.getDefaultPageId()!=null){
                eslTabDao.setPushTimeByTinyIP(tinyIP,networkIP,GID_index);
            }else if(GID_index!=eslTab.getGidIndex()||!networkIP.equals(eslTab.getNetworkIP())){

                eslTabDao.updateNetworkIPAndGid(networkIP,GID_index,tinyIP);

            }
            pushK0(channel,tinyIP);

            return;
        }


        if(keyStatus==1&&eslTab!=null){//按键状态：0=正常唤醒；1=按键唤醒；2:上次没有听到通道的广播; 3:未收到系统回复休眠,但是听到广播;4:是初始新ESL进入；5 长按键
            if(GID_index!=eslTab.getGidIndex()||!networkIP.equals(eslTab.getNetworkIP())){
                eslTabDao.updateNetworkIPAndGid(networkIP,GID_index,tinyIP);
            }
            if(eslTab.getDefaultPageId()!=null)
            pushK0Ka(channel, eslTab);
            else pushK0(channel,tinyIP);
            return;
        }


        int feature=szRecvBuf[18]+(szRecvBuf[19]<<8);
        boolean isMagnetic = (feature & 1) == 1;  //是否带磁性
        boolean isLed=(feature & 2) == 2;          //是否带led灯
        boolean isMultiPage=(feature & 4) == 4;     //是否支持多页
        boolean isHeartBeat=(feature & 8) == 8;     //是否支持心跳
        boolean isFreezing=(feature & 16) == 16;    //是否冷冻

        int screenSize=szRecvBuf[20]+(szRecvBuf[21]<<8);
        int pixel_x=szRecvBuf[22]+(szRecvBuf[23]<<8);
        int pixel_y=szRecvBuf[24]+(szRecvBuf[25]<<8);

        int screenColor_list = szRecvBuf[26];
        StringBuilder sb = new StringBuilder();
        String screenColor=sb.append((screenColor_list & 1) == 1?"B":"")                  //是否支持黑色
                .append((screenColor_list & 2) == 2?"W":"")            //是否支持白色
                .append((screenColor_list & 4) == 4?"R":"")           //是否支持红色
                .append((screenColor_list & 8) == 8?"Y":"")            //是否支持黄色
                .append((screenColor_list & 128) == 128?"M":"")       //是否支持彩色
                .toString();

        int maxPages=szRecvBuf[27];             //支持最大页数
        int firmwareId=szRecvBuf[28]+(szRecvBuf[29]<<8);//固件ID



        if(eslTab==null||keyStatus==4){
            EslFirmware eslFirmware =eslFirmwareDao.findById(firmwareId).orElse(null);
            if (eslFirmware == null) {
                eslFirmware = new EslFirmware();
                eslFirmware.setId(firmwareId);
                eslFirmware.setName(firmwareId+"");
                eslFirmware.setMagnet(isMagnetic+"");
                eslFirmware.setFreezer(isFreezing+"");
                eslFirmware.setLed(isLed+"");
                eslFirmware.setMpd(isMultiPage+"");
                eslFirmware.setHeartbeat(isHeartBeat+"");
                eslFirmware.setDpi(pixel_x + "*"+pixel_y);
                eslFirmware.setResolution_x(pixel_x);
                eslFirmware.setResolution_y(pixel_y);
                eslFirmware.setScreen_color(screenColor);
                eslFirmware.setScreen_size(screenSize);
                eslFirmware.setMax_page_num(maxPages);
                eslFirmware.setDirection("0");
                eslFirmwareDao.save(eslFirmware);
            }

        eslTabDao.insertOrUpdate(tinyIP,networkIP,versionStr,GID_index,firmwareId);

        }
        pushK0(channel,tinyIP);//否则调用K0推送方法

    }

    /**
     * 发送P15,kb,P16协议，即快速响应方案的价签数据传输协议组
     * @param apid  基站id
     * @param channel  基站对应的channel
     * @param gids     GID_index集合
     * @throws IOException
     */
    public void sendImgCmd(String apid,Channel channel,List<Integer>gids) throws IOException {
        if(channel==null)return;
        synchronized (channel){
            if(gids==null||gids.size()==0){
                gids=eslTabDao.findGid(apid);         //查询标签表中0<downloadTimes<6的标签ip,
                if(gids==null||gids.size()==0){
                    return;
                }
            }
            sendP15(channel,gids,apid);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                LOGGER.warn("sendKb sleep error:[{}]",e.getMessage());
            }
            for(Integer gid:gids){

                EslTab eslTab=eslTabDao.findByNetworkIPAndGid(apid,gid);
                if(eslTab==null){
                    LOGGER.warn("esltab is null");
                    return;
                }
                if(eslTab.getDownloadTimes()>=6){
                    if(eslTab.getPushTimes()>0){
                        eslTabDao.resetPushTimesById(eslTab.getTinyIP());
                    }
                    LOGGER.warn("downloadTimes more than 6,tinyIP:[{}],gid:[{}]",eslTab.getTinyIP(),eslTab.getGidIndex());
                    interfacesService.sendUpdateResult(eslTab.getTinyIP(),"1002","基站接收数据异常");
                    return;
                }
                if(!sendKb(channel,eslTab)){
                    return;
                };
            }
            sendP16(channel,gids,apid);
        }


    }


    /**
     * 发送P15
     * @param channel
     * @param gids   保存的gids列表
     * @throws IOException
     */
    public void sendP15(Channel channel,List<Integer>gids,String apid)  {
        byte [] ss=new byte[1024];



        int eslNum=gids.size();

        LOGGER.info("sendP15,apid:[{}]",apid);


        int ssIndex=0;
        ss[ssIndex++]=0x1B;
        ss[ssIndex++]=0;
        ss[ssIndex++]='P';
        ss[ssIndex++]=(byte)appID;
        ss[ssIndex++]=(byte)(appID>>8);
        for(int i=0;i<4;i++){
            ss[ssIndex++]=0;
        }
        ss[ssIndex++]=0x0f;


        ss[ssIndex++]=(byte)eslNum;
        ss[ssIndex++]=(byte)(eslNum>>8);

        for(Integer gid:gids){
            ss[ssIndex++]=(byte)gid.intValue();
            ss[ssIndex++]=(byte)(gid>>8);
        }
        ssIndex+=2;//预留两位，作为保留字节
        ss=Arrays.copyOf(ss,ssIndex+1);     //截取字节数组的有效长度
        ss[ss.length-1]=getSum(ss);
        channelWrite(channel,ss);
    }

    /**
     * 发送p16
     * @param channel
     * @throws IOException
     */
    public void sendP16(Channel channel,List<Integer>gids,String apid)  {
        if(channel==null)return;
        byte [] ss=new byte[13];
        int ssIndex=0;
        ss[ssIndex++]=0x1B;
        ss[ssIndex++]=11;
        ss[ssIndex++]='P';
        ss[ssIndex++]=(byte)appID;
        ss[ssIndex++]=(byte)(appID>>8);
        for(int i=0;i<4;i++){
            ss[ssIndex++]=0;
        }

        ss[ssIndex++]=0x10;
        ss[ssIndex++]=0;
        ss[ssIndex++]=0;
        ss[ssIndex++]=getSum(ss);
        channelWrite(channel,ss);
        LOGGER.info("sendP16,apid:[{}]",apid);
        eslTabDao.resetPushTimes(gids,apid);  //下发数据表示outputNum清零


    }


    /**
     * 处理接收到基站返回的p16
     * @param channel
     * @param szRecvBuf   接收到的数据
     * @throws IOException
     */
    public void p16Manager(String apid,Channel  channel,int[] szRecvBuf) throws IOException {
        int eslNum=szRecvBuf[10]+(szRecvBuf[11]<<8);
        if(eslNum>0){
            List<Integer> gid_indexs = new ArrayList<>();
            for(int i=0;i<eslNum;i++){
                gid_indexs.add(szRecvBuf[12+i*2]+(szRecvBuf[13+i*2]<<8));
            }
            LOGGER.info("p16 lack of Kb,GID_index:[{}]",gid_indexs.toString());
            sendImgCmd(apid,channel,gid_indexs);
            return;
        }
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            LOGGER.warn("sendKd sleep error:[{}]",e.getMessage());
        }
        sendImgCmd(apid,channel,null);

    }

    /**
     * P17,告知基站删除指定价签
     * @param eslTab
     * @throws IOException
     */
    public void sendP17(EslTab eslTab)  {

        if(eslTab==null)return;
        byte [] ss=new byte[17];

        String apid=eslTab.getNetworkIP();
        Integer gid=eslTab.getGidIndex();
        Channel channel = bsChannelMap.get(apid);
        if(channel==null)return;
        LOGGER.info(apid+"---P17,tinyIP[{}],gid:[{}],apid[{}]",eslTab.getTinyIP(),gid,apid);
        int ssIndex=0;
        ss[ssIndex++]=0x1B;
        ss[ssIndex++]=15;
        ss[ssIndex++]='P';
        ss[ssIndex++]=0;
        ss[ssIndex++]=0;
        for(int i=0;i<4;i++){
            ssIndex++;
        }
        ss[ssIndex++]=0x11;
        ss[ssIndex++]=1;   //价签数量低8位
        ss[ssIndex++]=0;  //价签数量高8位

            ss[ssIndex++]=(byte)gid.intValue();
            ss[ssIndex++]=(byte)(gid>>8);
        ss[16]=getSum(ss);

        channelWrite(channel,ss);
    }

    /**
     * 发送Kd
     * @param channel
     * @param eslTab   待更新的价签对象
     * @throws IOException
     */
    public boolean sendKb(Channel channel,EslTab eslTab) throws IOException {
        if(channel==null||eslTab==null){
            LOGGER.warn("sendKb error:channel or eslTab is null");
            return false;
        }

        byte [] ss=new byte[100*1024];
        int ssIndex=0;
        ss[ssIndex++]=0x1C;
        ss[ssIndex++]=0;
        ss[ssIndex++]='K';
        ss[ssIndex++]=(byte)appID;
        ss[ssIndex++]=(byte)(appID>>8);
        for(int i=0;i<4;i++){
            ss[ssIndex++]=0;
        }
        ss[ssIndex++]='b';
        ss[ssIndex++]=1;
        ss[ssIndex++]=0;
        ssIndex+=2;
        int szSize=0;
            byte[]sz=generateImgPackage(eslTab);
            if(sz==null){
                return false;
            }
            szSize+=sz.length;
            System.arraycopy(sz,0,ss,ssIndex,sz.length);  //ssIndex+copyIndex表示ss数组的当前指针
            ssIndex+=sz.length;
        ss[12]=(byte)szSize;
        ss[13]=(byte)(szSize>>8);
        ss=Arrays.copyOf(ss,ssIndex+1);
        ss[ss.length-1]=getSum(ss);

        channelWrite(channel,ss);
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            LOGGER.warn("sendKb sleep error:[{}]",e.getMessage());
        }
        LOGGER.info("sendKb,tinyIP;[{}]",eslTab.getTinyIP());
        return true;
    }

    /**
     * 生成Kb中的imgPackage
     * @param eslTab
     * @return
     * @throws IOException
     */
    private byte[] generateImgPackage(EslTab eslTab) throws IOException {
        String imgPath="sn_img_bin/"+eslTab.getStoreCode()+"/"+eslTab.getTinyIP()+"-"+eslTab.getDefaultPageId()+".bin";
        File file=new File(imgPath);  //图形描述字文件
        if(!file.exists()){
            LOGGER.warn("点阵图形不存在,tinyIP:[{}],imgPath:[{}]",eslTab.getTinyIP(),imgPath);
            if(eslTab.getDefaultPageId()==null){
                eslTabDao.deleteById(eslTab.getTinyIP());
                return null;
            }
            eslTabDao.resetDownTimes(eslTab.getTinyIP());
            return null;
        }
        byte[] sz2=new byte[(int)file.length()];                    //sz2字节数组用于保存从图形描述字文件读到的内容


        FileInputStream fis=new FileInputStream(file);
        fis.read(sz2);
        fis.close();


        byte[] sz={                                              //Ka数据包头

                0, 0,                 //GID_index[2];组ID与组内ID的结合
                0, 0,                //img_size[2]

                20,19,5,15,2,15,24,33,
                0,0x00,
                0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
                0x00,
                0x08,               //价签休眠时间，秒，生产环境设置为8秒
                0x00,
                0x00
        };
        int a=sz.length;
        sz= Arrays.copyOf(sz,sz.length+sz2.length+1);            //sz与sz2拼接，最终长度为sz长度+sz2长度+1，最后一位存储校验和
        System.arraycopy(sz2,0,sz,a,sz2.length);                     //sz与sz2拼接


//------------------------------------------------------------------------------
        sz[0]=(byte)eslTab.getGidIndex().intValue();
        sz[1]=(byte)(eslTab.getGidIndex()>>8);
        sz[2]=(byte)(sz2.length+22);
        sz[3]=(byte)((sz2.length+22)>>8);
        //------------------sz校验和start
        sz[sz.length-1]=getSum(sz);
        //-------------------sz校验和end
//----------------------------------------------------------------------------------
        return sz;
    }





    /**
     * 上位机通知基站上报价签状态
     * @param channel
     * @param flag  flag=0表示通知基站上报价签状态，flag=1表示通知基站刷屏
     */
    public void sendP13(String apid,Channel channel,int flag){
        byte [] ss=new byte[15];
        int ssIndex=0;
        ss[ssIndex++]=0x1b;
        ss[ssIndex++]=13;
        ss[ssIndex++]='P';
        ss[ssIndex++]=(byte)appID;
        ss[ssIndex++]=(byte)(appID>>8);
        for(int i=0;i<4;i++){
            ss[ssIndex++]=0;
        }
        ss[ssIndex++]=0x0d;
        ss[ssIndex++]=(byte) flag;  //flag=0表示通知基站上报价签状态，flag=1,表示通知基站刷屏
        ss[14]=getSum(ss);
        channelWrite(channel,ss);
        LOGGER.info("sendP13,apid:[{}]",apid);
    }


    /**
     * 返回P14给基站
     * @param channel  价签id
     */
    public void sendP14(Channel channel){
        byte[]ss=new byte[15];
        int ssIndex=0;
        ss[ssIndex++]=0x1b;
        ss[ssIndex++]=13;
        ss[ssIndex++]='P';
        ss[ssIndex++]=(byte)appID;
        ss[ssIndex++]=(byte)(appID>>8);
        for(int i=0;i<4;i++){
            ss[ssIndex++]=0;
        }
        ss[ssIndex++]=0x0e;
        int eslNum=0;


        ss[ssIndex++]=(byte)eslNum;
        ss[ssIndex++]=(byte)(eslNum>>8);

        ss[14]=getSum(ss);
        channelWrite(channel,ss);
    }




}

    

